namespace System.Linq.Expressions
{
    public static class ExpressionEx
    {
        public static Expression<Func<double[], double>> ArrrayMaximum
        {
            get
            {
                Func<double[], double> GetMaximum = X =>
                {
                    var max = double.NegativeInfinity;
                    var L = X.Length;
                    for(var i = 0; i < L; i++)
                        if(X[i] > max) max = X[i];
                    return max;
                };

                Expression<Func<double[], double>> result = X => GetMaximum(X);
                return result;
            }
        }

        public static Expression<Func<double[], double>> ArrrayMinimum
        {
            get
            {
                Func<double[], double> GetMaximum = X =>
                {
                    var min = double.PositiveInfinity;
                    var L = X.Length;
                    for(var i = 0; i < L; i++)
                        if(X[i] < min) min = X[i];
                    return min;
                };

                Expression<Func<double[], double>> result = X => GetMaximum(X);
                return result;
            }
        }

        public static Expression<Func<double, double>> GetDifferential(this Expression<Func<double, double>> f,
            Func<MethodCallExpression, Expression> FunctionDifferentiator = null)
        {
            var visitor = new DifferentialVisitor();
            if(FunctionDifferentiator != null)
                visitor.MethodDifferential += (s, e) => e.DifferentialExpression = FunctionDifferentiator(e.Method);
            var d = visitor.Visit(f);
            return (Expression<Func<double, double>>)d;
        }

        public static Expression<Func<TFirstParam, TResult>>
            Compose<TFirstParam, TIntermediate, TResult>(
            this Expression<Func<TFirstParam, TIntermediate>> first,
            Expression<Func<TIntermediate, TResult>> second)
        {
            var param = Expression.Parameter(typeof(TFirstParam), "param");

            var newFirst = first.Body.Replace(first.Parameters[0], param);
            var newSecond = second.Body.Replace(second.Parameters[0], newFirst);

            return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
        }

        public static Expression Replace(this Expression expression,
            Expression searchEx, Expression replaceEx)
        {
            return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
        }

        public static Expression<Func<TSource, bool>> IsNotNull<TSource, TKey>(
            this Expression<Func<TSource, TKey>> expression)
        {
            return expression.Compose(key => key != null);
        }
    }

    public static class PredicateBuilder
    {
        public static Expression<Func<T, bool>> True<T>() { return f => true; }
        public static Expression<Func<T, bool>> False<T>() { return f => false; }

        public static Expression<Func<T, bool>> Or<T>(
            this Expression<Func<T, bool>> expr1,
            Expression<Func<T, bool>> expr2)
        {
            var secondBody = expr2.Body.Replace(
                expr2.Parameters[0], expr1.Parameters[0]);
            return Expression.Lambda<Func<T, bool>>
                  (Expression.OrElse(expr1.Body, secondBody), expr1.Parameters);
        }

        public static Expression<Func<T, bool>> And<T>(
            this Expression<Func<T, bool>> expr1,
            Expression<Func<T, bool>> expr2)
        {
            var secondBody = expr2.Body.Replace(
                expr2.Parameters[0], expr1.Parameters[0]);
            return Expression.Lambda<Func<T, bool>>
                  (Expression.AndAlso(expr1.Body, secondBody), expr1.Parameters);
        }
    }

    //class ReplaceMethodCallVisitor : MathService.MathParser.ExpressionVisitorEx
    //{
    //    readonly MethodInfo methodToReplace;
    //    readonly Func<IReadOnlyList<Expression>, Expression> replacementFunction;

    //    public ReplaceMethodCallVisitor(
    //        MethodInfo methodToReplace,
    //        Func<IReadOnlyList<Expression>, Expression> replacementFunction)
    //    {
    //        this.methodToReplace = methodToReplace;
    //        this.replacementFunction = replacementFunction;
    //    }

    //    protected override Expression VisitMethodCall(MethodCallExpression node)
    //    {
    //        if(node.Method == methodToReplace)
    //            return replacementFunction(node.Arguments);

    //        return base.VisitMethodCall(node);
    //    }
    //}
}